#pragma once
#include <string>
#include <sstream>
#include "Node.h"

using namespace std;
/**
*
*/
template<class T>
class List
{

        class Iterator
        {
        public:
                typedef Iterator self_type;
                typedef Node<T> value_type;
                typedef Node<T>& reference;
                typedef Node<T>* pointer;
                typedef std::forward_iterator_tag iterator_category;
                typedef int difference_type;

                Iterator(pointer ptr) : ptr_(ptr) { } //Constructor
                self_type operator++() { self_type i = *this; ptr_ = ptr_->next; return i; }
                self_type operator++(int junk) { ptr_ = ptr_->next; return *this; }
                reference operator*() { return *ptr_; }
                pointer operator->() { return ptr_; }
                bool operator==(const self_type& rhs) { return ptr_ == rhs.ptr_; }
                bool operator!=(const self_type& rhs) { return ptr_ != rhs.ptr_; }

        protected:
                pointer ptr_;
        };
public:



        List()
        {
                head = tail = nullptr;
        }

        List(const List &list)
        {
                Node<T> *n = list.head;
                while (n != nullptr)
                {
                        this->add_at_head(n->data);
                        n = n->next;
                }
        }

        ~List()
        {
                Node<T> *n = head;
                Node<T> *d;
                while (n != nullptr)
                {
                        d = n;
                        n = n->next;
                        delete d;
                }
        }

        List &operator=(const List<T> &list)
        {
                Node<T> *n = list.head;
                while (n != nullptr)
                {
                        this->add_at_head(n->data);
                        n = n->next;
                }
                return *this;
        }

        void add_at_head(T tal)
        {
                if (head == nullptr)
                {
                        head = new Node<T>(tal);
                        tail = head;
                }
                else
                {
                        Node<T> *n = new Node<T>(tal);
                        n->next = head;
                        head = n;
                }
        }

        void clear()
        {
                Node<T> *n = head;
                Node<T> *d;
                while (n != nullptr)
                {
                        d = n;
                        n = n->next;
                        delete d;
                }
                head = tail = nullptr;
        }

        void remove(Node<T> *d)
        {
                Node<T> *b = nullptr;
                if (d == nullptr || head == nullptr)
                        return;
                else if (d == head && head == tail)
                {
                        delete d;
                        head = tail = nullptr;
                }
                else if (d == head )
                {
                        head = head->next;
                        delete d;
                }
                else if (d == tail)
                {
                        b = this->nodeBefore(d);
                        b->next = nullptr;
                        tail = b;
                        delete d;
                }
                else
                {
                        b = nodeBefore(d);
                        b->next = d->next;
                        delete d;
                }
        }

        Node<T> *nodeBefore(Node<T> *d)
        {
                Node<T> *s = head;
                while (s != nullptr)
                {
                        if (s->next == d)
                                return s;
                        else
                                s = s->next;
                }
                return nullptr;
        }

        Node<T> *search(T &info)
        {
                Node<T> *n = head;

                if (n == nullptr)
                        return nullptr;
                else
                {
                        do
                        {
                                if (n->data == info)
                                        return n;
                                n = n->next;
                        } while (n != nullptr);
                }

                return nullptr;
        }

        string str()
        {
                ostringstream oss;

                Node<T> *n = head;
                if (n == nullptr)
                        return "";
                else
                {
                        do
                        {
                                oss << n->data << " ";
                                n = n->next;
                        } while (n != nullptr);
                }

                return oss.str();
        }

        Iterator begin()
        {
                return Iterator(head);
        }

        Iterator end()
        {
                return Iterator(nullptr);
        }

        void split_even_odd(List<T> &odd, List<T> &even)
        {
                Node<T> *n = this->head;
                while (n != nullptr)
                {
                        if (n->data % 2 == 0)
                                even.add_at_head(n->data);
                        else
                                odd.add_at_head(n->data);
                        n = n->next;
                }
        }

protected:
        Node<T> *head;
        Node<T> *tail;
};

